﻿using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using NodeBasedSkillEditor.Model;
using NodeBasedSkillEditor.View;

namespace NodeBasedSkillEditor.ViewModel
{
    public class VM_Port : ObservableObject
    {
        public const int MaxDisplayValueLength = 24;

        #region Bind Properties
        private string _portName;
        public string PortName {
            get => _portName;
            set { _portName = value; OnPropertyChanged(); UpdatePortDisplayName(); }
        }

        public string PortFullName { get; private set; } = "";

        private string _portDisplayName = "";
        public string PortDisplayName {
            get { return _portDisplayName; }
            set { _portDisplayName = value; OnPropertyChanged(); }
        }

        private bool _isMultiConnectable = false;
        public bool IsMultiConnectable {
            get => _isMultiConnectable;
            set { _isMultiConnectable = value; OnPropertyChanged(); }
        }

        private bool _isListPort = false;
        public bool IsListPort {
            get { return _isListPort; }
            set { _isListPort = value; OnPropertyChanged(); }
        }

        // 如果port属于Group，此属性为true时被连接的话，会扩展Group增加新端口
        private bool _isExpandPort = false;
        public bool IsExpandPort {
            get => _isExpandPort;
            set { _isExpandPort = value; OnPropertyChanged(); UpdatePortDisplayName(); }
        }

        private Color _portColor = DAGHelper.UnknownDataColor;
        public Color PortColor {
            get => _portColor;
            set {
                _portColor = value;
                BindingPortCtrl?.ChangePortColor(value);
                //OnPropertyChanged();
                OnPortColorChanged?.Invoke(value);
            }
        }

        public event Action<Color> OnPortColorChanged;
        #endregion

        public Point Position => BindingPortCtrl.Position;

        public EPortIOType IOType { get; private set; }
        public bool IsNormalPortInGroup => ParentGroup != null && !IsExpandPort;

        BasePort _bindPortData = null;
        public BasePort BindPortData {
            get => _bindPortData;
            set {
                if (_bindPortData != value)
                {
                    var prevType = _bindPortData?.GetType();
                    var newType = value?.GetType();
                    _bindPortData = value;
                    if (prevType != newType)
                    {
                        UpdatePortColorByDataType();
                    }
                    UpdatePortDisplayName();
                    IsMultiConnectable = DAGHelper.ShouldPortBeMultiConnectable(_bindPortData, IOType);
                }
            }
        }

        public Type PortType { get => _bindPortData?.GetType(); }

        public Type DataType { get => _bindPortData?.GetDataType(); }

        public event Action<uint, object, object> OnPortValueChanged;
        // 注意，对这个Value赋值，将会产生操作历史，目前仅在Inspector中被对应的FieldControl绑定
        // 如果要在不产生历史的情况下进行赋值（例如操作历史发生回退或前进时赋值），应该使用方法ChangeValueWithoutChangeHistory
        public object Value {
            get {
                var valuePort = _bindPortData as ValuePort;
                if (valuePort != null) { return valuePort.GetValue(); }
                else return null;
            }
            set {
                var valuePort = _bindPortData as ValuePort;
                if (valuePort != null)
                {
                    var oldValue = valuePort.GetValue();
                    if (valuePort.SetValue(value))
                    {
                        UpdatePortDisplayName();
                        OnPropertyChanged();
                        OnPortValueChanged?.Invoke(Id, oldValue, value);
                    }
                }
            }
        }

        public void ChangeValueWithoutChangeHistory(object newValue)
        {
            var valuePort = _bindPortData as ValuePort;
            if (valuePort != null)
            {
                if (valuePort.SetValue(newValue))
                {
                    UpdatePortDisplayName();
                    OnPropertyChanged("Value");
                }
            }
        }

        public bool IsFlowPort { get => DataType == typeof(FlowPort); }

        public uint LocalId { get; private set; }
        public uint Id { get; private set; }
        public VM_Node ParentNode { get; private set; }
        public VM_PortGroup ParentGroup { get; private set; }
        public VM_PortCluster ParentCluster { get; private set; }
        public int TypeGroupId { get; private set; }
        public EPortContextMode ContextMode { get; private set; }
        public int IndexInParentGroup => ParentGroup != null ? ParentGroup.SubPorts.IndexOf(this) : -1;

        public bool CanRelayContext { get; private set; }

        public bool IsConnected {
            get => ParentNode.ParentDAG.LinkManager.IsPortLinked(this);
        }

        public PortControl BindingPortCtrl { get; private set; }

        public RelayCommand<MouseButtonEventArgs> OnLMBDownCmd { get; private set; }
        private void OnLMBDown(MouseButtonEventArgs e)
        {
            ParentNode.ParentDAG.OnLMBDownOnPort(e, this);
        }

        public RelayCommand<MouseButtonEventArgs> OnLMBUpCmd { get; private set; }
        private void OnLMBUp(MouseButtonEventArgs e)
        {
            ParentNode.ParentDAG.OnLMBUpOnPort(e, this);
        }

        public RelayCommand<MouseButtonEventArgs> OnRMBUpCmd { get; private set; }
        private void OnRMBUp(MouseButtonEventArgs e)
        {
            ParentNode.ParentDAG.OnRMBUpOnPort(e, this);
        }

        public RelayCommand<MouseEventArgs> OnMouseEnterCmd { get; private set; }
        private void OnMouseEnter(MouseEventArgs e)
        {
            ParentNode.ParentDAG.OnMouseEnterPort(e, this);
        }

        public RelayCommand<MouseEventArgs> OnMouseLeaveCmd { get; private set; }
        private void OnMouseLeave(MouseEventArgs e)
        {
            ParentNode.ParentDAG.OnMouseLeavePort(e, this);
        }

        public VM_Port(uint localId, VM_Node parentNode, string portName, EPortIOType ioType, BasePort portData,
            int typeGroupId, EPortContextMode contextMode, bool isListPort)
        {
            ParentNode = parentNode;
            LocalId = localId;
            Id = DAGHelper.PortLocalId_To_PortGlobalId(ParentNode.LocalId, LocalId);
            PortName = portName;
            IsListPort = isListPort;
            IOType = ioType;
            BindPortData = portData;
            TypeGroupId = typeGroupId;
            ContextMode = contextMode;

            CanRelayContext = DAGHelper.CanPortRelayContext(isListPort, IOType, ContextMode);

            OnLMBDownCmd = new RelayCommand<MouseButtonEventArgs>(OnLMBDown);
            OnLMBUpCmd = new RelayCommand<MouseButtonEventArgs>(OnLMBUp);
            OnRMBUpCmd = new RelayCommand<MouseButtonEventArgs>(OnRMBUp);
            OnMouseEnterCmd = new RelayCommand<MouseEventArgs>(OnMouseEnter);
            OnMouseLeaveCmd = new RelayCommand<MouseEventArgs>(OnMouseLeave);
        }

        public void SetupBindingPortCtrl(PortControl portCtrl)
        {
            BindingPortCtrl = portCtrl;
            portCtrl.DataContext = this;
            BindingPortCtrl?.ChangePortColor(PortColor);
            UpdatePortDisplayName();
        }

        public void SetParentGroup(VM_PortGroup group)
        {
            ParentGroup = group;
        }

        public void SetParentCluster(VM_PortCluster cluster)
        {
            ParentCluster = cluster;
        }

        public VM_Port Expand()
        {
            return ParentNode.IncreseNewPortByExpandPort(this);
        }

        public void UpdatePortColorByDataType()
        {
            PortColor = DAGHelper.GetColorFromDataType(DataType);
        }

        char[] _returnSign = new char[2] { '\r', '\n' };
        public void UpdatePortDisplayName()
        {
            if (IsExpandPort)
            {
                PortFullName = $"{PortName}...";
                PortDisplayName = PortFullName;
                return;
            }

            if (IsNormalPortInGroup)
            {
                PortFullName = $"{PortName}{IndexInParentGroup}";
            }
            else
            {
                PortFullName = PortName;
            }

            if (!IsListPort && IOType == EPortIOType.Input && !IsConnected && BindPortData != null)
            {
                if (BindPortData.GetType().IsSubclassOf(typeof(ValuePort)))
                {
                    var vp = BindPortData as ValuePort;
                    var valueStr = vp.GetValue()?.ToString();
                    if (valueStr != null)
                    {
                        var originLen = valueStr.Length;
                        var firstReturnSign = valueStr.IndexOfAny(_returnSign);
                        if (firstReturnSign != -1)
                        {
                            valueStr = valueStr.Substring(0, firstReturnSign);
                        }
                        if (valueStr.Length > MaxDisplayValueLength)
                        {
                            valueStr = valueStr.Substring(0, MaxDisplayValueLength);
                        }
                        var suffix = valueStr.Length < originLen ? "..." : "";
                        PortDisplayName = $"{PortFullName}: {valueStr}{suffix}";
                    }
                    else if (BindPortData is StringPort)
                    {
                        PortDisplayName = $"{PortFullName}:";
                    }
                    return;
                }
            }
            PortDisplayName = PortFullName;
        }
    }
}
